/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */

#include <check.h>
#include <stdlib.h>

#include "compatibility.h"

#define NUM_REPEATS 100

quad_vertex_index QUAD_PERMUTATIONS[][4] = {
  {0, 1, 2, 3},
  {1, 2, 3, 0},
  {2, 3, 0, 1},
  {3, 0, 1, 2},

  {0, 3, 2, 1},
  {1, 0, 3, 2},
  {2, 1, 0, 3},
  {3, 2, 1, 0},
};

static void shuffle(vertex_index *array, size_t n) {
  for (size_t i = 0; i < n - 1; i++) {
    size_t j = i + rand() % (n - i);
    vertex_index v = array[i];
    array[i] = array[j];
    array[j] = v;
  }
}

static void random_labels(vertex_index *labels) {
  vertex_index vertices[256];
  for (size_t i = 0; i < 256; i++) vertices[i] = i;
  shuffle(vertices, 256);
  memcpy(labels, vertices, 16 * sizeof(*vertices));
}

START_TEST(test_share_none) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    ck_assert_msg(hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing no vertices should be considered compatible.");
  }
} END_TEST

START_TEST(test_share_one_vertex) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    labels[8 + rand() % 8] = labels[rand() % 8];

    ck_assert_msg(hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing one vertex should be considered compatible.");
    ck_assert_msg(hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing one vertex should be considered compatible.");
  }
} END_TEST

START_TEST(test_share_edge) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    int edge_a = rand() % 12;
    int edge_b = rand() % 12;

    if (rand() % 2 == 0) {
      labels[8 + HEX_EDGES[edge_a][0]] = labels[HEX_EDGES[edge_b][0]];
      labels[8 + HEX_EDGES[edge_a][1]] = labels[HEX_EDGES[edge_b][1]];
    }
    else {
      labels[8 + HEX_EDGES[edge_a][0]] = labels[HEX_EDGES[edge_b][1]];
      labels[8 + HEX_EDGES[edge_a][1]] = labels[HEX_EDGES[edge_b][0]];
    }

    ck_assert_msg(hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing one edge should be considered compatible.");
    ck_assert_msg(hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing one edge should be considered compatible.");
  }
} END_TEST

START_TEST(test_share_diagonal) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    int diagonal_id = rand() % 4;

    labels[8 + diagonal_id] = labels[rand() % 8];
    do {
    labels[8 + 4 + (diagonal_id + 2) % 4] =
      labels[rand() % 8];
    } while (labels[8 + 4 + (diagonal_id + 2) % 4] == labels[8 + diagonal_id]);

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing a quad diagonal should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing a quad diagonal should not be considered compatible.");
  }
} END_TEST

START_TEST(test_share_quad_diagonal) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    hex_facet_index quad_id = rand() % 6;
    quad_vertex_index i_a = rand() % 4;
    quad_vertex_index i_b = (i_a + 2) % 4;

    vertex_index v_a = 8 + HEX_FACET_CONTENTS[quad_id][i_a];
    vertex_index v_b = 8 + HEX_FACET_CONTENTS[quad_id][i_b];

    labels[v_a] = labels[rand() % 8];
    do {
      labels[v_b] = labels[rand() % 8];
    } while (labels[v_b] == labels[v_a]);

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing a diagonal should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing a diagonal should not be considered compatible.");
  }
} END_TEST

START_TEST(test_share_three_vertices) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    vertex_index hex_a[8];
    vertex_index ids[8] = {0, 1, 2, 3, 4, 5, 6, 7};

    memcpy(hex_a, labels, sizeof(hex_a));
    shuffle(hex_a, 8);
    shuffle(ids, 8);

    for (size_t i = 0; i < 3; i++) labels[8 + ids[i]] = hex_a[i];

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing three vertices should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing three vertices should not be considered compatible.");
  }
} END_TEST

START_TEST(test_share_quad) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    hex_facet_index f_a = rand() % 6;
    hex_facet_index f_b = rand() % 6;

    int perm_id = 4 + rand() % 4;

    for (quad_vertex_index i = 0; i < 4; i++) {
      labels[8 + HEX_ORIENTED_FACET_CONTENTS[f_a][QUAD_PERMUTATIONS[perm_id][i]]] =
        labels[HEX_ORIENTED_FACET_CONTENTS[f_b][i]];
    }

    ck_assert_msg(hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing a quadrangle should be considered compatible.");
    ck_assert_msg(hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing a quadrangle should be considered compatible.");
  }
} END_TEST

START_TEST(test_share_misoriented_quad) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    hex_facet_index f_a = rand() % 6;
    hex_facet_index f_b = rand() % 6;

    int perm_id = rand() % 4;

    for (quad_vertex_index i = 0; i < 4; i++) {
      labels[8 + HEX_ORIENTED_FACET_CONTENTS[f_a][QUAD_PERMUTATIONS[perm_id][i]]] =
        labels[HEX_ORIENTED_FACET_CONTENTS[f_b][i]];
    }

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing a quadrangle with improper orientation should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing a quadrangle with improper orientation should not be considered compatible.");
  }
} END_TEST

START_TEST(test_share_non_quad) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    hex_facet_index f_id = rand() % 6;

    hex_facet_index f_a = rand() % 6;
    hex_facet_index f_b = HEX_OPPOSITE_FACET[f_a];

    int num_a = 1 + rand() % 3;

    hex_vertex_index ids[4];
    memcpy(ids, HEX_FACET_CONTENTS[f_a], num_a * sizeof(*ids));
    memcpy(ids + num_a, HEX_FACET_CONTENTS[f_b], 4 - num_a * sizeof(*ids));

    for (quad_vertex_index i = 0; i < 4; i++)
      labels[8 + ids[i]] = labels[HEX_ORIENTED_FACET_CONTENTS[f_id][i]];

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing 4 vertices not forming a quadrangle should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing 4 vertices not forming a quadrangle should not be considered compatible.");
  }
} END_TEST

START_TEST(test_share_many_vertices) {
  for (size_t i = 0; i < NUM_REPEATS; i++) {
    vertex_index labels[16];
    random_labels(labels);

    vertex_index hex_a[8];
    vertex_index ids[8] = {0, 1, 2, 3, 4, 5, 6, 7};

    memcpy(hex_a, labels, sizeof(hex_a));
    shuffle(hex_a, 8);
    shuffle(ids, 8);

    for (size_t i = 0; i < (size_t)(5 + rand() % 4); i++) labels[8 + ids[i]] = hex_a[i];

    ck_assert_msg(!hex_compatibility(labels + 0, labels + 8),
                  "Two hexahedra sharing three vertices should not be considered compatible.");
    ck_assert_msg(!hex_compatibility(labels + 8, labels + 0),
                  "Two hexahedra sharing three vertices should not be considered compatible.");
  }
} END_TEST

Suite *compatibility_suite(void) {
  Suite *s = suite_create("Compatibility");

  TCase *tcase = tcase_create("Compatibility");
  tcase_add_test(tcase, test_share_none);
  tcase_add_test(tcase, test_share_one_vertex);
  tcase_add_test(tcase, test_share_edge);
  tcase_add_test(tcase, test_share_diagonal);
  tcase_add_test(tcase, test_share_quad_diagonal);
  tcase_add_test(tcase, test_share_three_vertices);
  tcase_add_test(tcase, test_share_quad);
  tcase_add_test(tcase, test_share_misoriented_quad);
  tcase_add_test(tcase, test_share_non_quad);
  tcase_add_test(tcase, test_share_many_vertices);
  suite_add_tcase(s, tcase);

  return s;
}
